# -*- coding: utf8 -*-

'''
Module for reading and writing Musepack SV8 chapters.
Current realization uses musepack-tools for it. This
can be changed in future. Probably I'll switch to mutagen
for reading SV8 chapters in future, when mutagen will
support it.

Licence: GPLv2
Author: Joseph Botosh <rumly111@gmail.com>
'''

from configparser import ConfigParser
from configparser import Error as CPError
import os
import shutil
import tempfile
import logging
import subprocess

# cue_encoding = 'CP1251'

default_tags = {
    'Artist' : '',
    'Album'  : '',
    'Title'  : '',
    'Year'   : '',
    'Date'   : '',
    'Track'  : '',
    'Genre'  : '', 
    'Comment': 'Tagged with PyMpcChap', }

class MpcChapException(Exception):
    pass

def console_input(prompt='') -> str:
    return input(prompt)

class MpcChapters(object):
    def __init__(self):
        # self.mpc_file=None
        self.chapters=[]
        self.start_samples=[]

    def read_chapters_from(self,source : str, encoding=None):
        '''
        Read chapters from file, specified by source.
        source is a string, it can be a *.mpc, *.ini or *.cue
        file name.
        '''
        logging.debug('Reading chapters from "{}"'.format(source))
        if not os.path.exists(source):
            raise MpcChapException('{} does not exist'.format(source))

        if source.endswith('.mpc'):
            self._read_chapters_from_mpc(source,encoding)
        elif source.endswith('.ini'):
            self._read_chapters_from_ini(source,encoding)
        elif source.endswith('.cue'):
            self._read_chapters_from_cue(source,encoding)
        else:
            raise MpcChapException('Filename must be *.mpc, *.ini or *.cue')

    def _read_chapters_from_ini(self, source : str, encoding=None):
        cp = ConfigParser()
        cp.optionxform = str
        try:
            cp.read(source, encoding=encoding)
        except CPError as e:
            raise MpcChapException('Bad *.ini file')

        self.chapters = []
        self.start_samples = []
        for s in cp.sections():
            try:
                chapter_start_sample = int(s)
            except ValueError as e:
                raise MpcChapException('[chapter_start_sample] must contain decimals')
            tags = default_tags.copy()
            non_tags = ('gain', 'peak')
            for tag_name in cp.options(s):
                if tag_name not in non_tags:
                    tag_value = cp.get(s,tag_name).strip('"')
                    tags[tag_name] = tag_value
            self.chapters.append(tags)
            self.start_samples.append(s)
                

    def _read_chapters_from_mpc(self, source : str, encoding=None):
        tmp = tempfile.mktemp(prefix='pmc-',suffix='.ini')
        logging.debug('_read_chapters_from_mpc: tempfile:'+tmp)
        mpcchap_args = ['mpcchap',source,tmp]
        logging.debug(' '.join(mpcchap_args))
        devnull = open('/dev/null',mode='wb')
        result = subprocess.call(mpcchap_args,stdout=devnull,stderr=devnull)
        if result != 0:
            raise MpcChapException('mpcchap error')
        self._read_chapters_from_ini(tmp,encoding)
        # NOTE: this line may not be procecced if the previous fails
        os.remove(tmp)

    def _read_chapters_from_cue(self, source : str,
                                encoding = None,
                                input_callback = console_input):
        '''
        Read chapters from cue file <source>.
        <input_callback> is a function that returns a string.
        It is used to prompt for mpc filename if one not found.
        If you use _read_chapters_from_cue in console app, it
        is not nesessary to specify <input_callback>
        See "console_input" for callback interface
        '''

        # Search for proper *.mpc file
        cue_dir = os.path.dirname(os.path.abspath(source))
        cue_file = os.path.basename(source)
        dir_mpc_files = [ x for x in os.listdir(cue_dir) if x.endswith('.mpc') ]
        if len(dir_mpc_files) == 0:
            mpc_file = input_callback('Enter full path to mpc file: ')
        elif len(dir_mpc_files) == 1:
            mpc_file = os.path.join(cue_dir,dir_mpc_files[0])
        else:
            song = cue_file.rstrip('.cue')
            if os.path.exists(os.path.join(cue_dir,song+'.mpc')):
                mpc_file = os.path.join(cue_dir,song+'.mpc')
            else:
                mpc_file = input_callback('Enter full path to mpc file: ')

        logging.debug('_read_chapters_from_cue: mpc file is '+mpc_file)

        mpc_tmp = tempfile.mktemp(prefix='pmc-',suffix='.mpc')
        logging.debug('shutil.copyfile("{}","{}")'.format(mpc_file,mpc_tmp))
        shutil.copyfile(mpc_file,mpc_tmp)

        mpc_chap_args=['mpcchap',mpc_tmp,source]
        logging.debug(' '.join(mpc_chap_args))
        devnull = open('/dev/null','wb')
        result = subprocess.call(mpc_chap_args,stdout=devnull,stderr=devnull)
        if result != 0:
            raise MpcChapException('mpcchap error')

        self._read_chapters_from_mpc(mpc_tmp,encoding)
        self.__read_extra_tags_from_cue(source,encoding)

        os.remove(mpc_tmp)

    def __read_extra_tags_from_cue(self,source : str,
                                   cue_encoding = None):
        '''
        Read some extra information from cue file, which
        musepack-tools don't read.
        '''
        def apply_tags(name,value):
            for ch in self.chapters:
                ch[name]=value
                
        cue = open(source,encoding=cue_encoding)
        for line in cue.readlines():
            if line.startswith('TITLE'):
                album=line[5:].strip().strip('"')
                apply_tags('Album',album)
            elif line.startswith('REM GENRE'):
                genre=line[9:].strip().strip('"')
                apply_tags('Genre',genre)
            elif line.startswith('REM DATE'):
                year=line[8:].strip().strip('"')
                apply_tags('Year',year)
                apply_tags('Date',year)
            elif line.startswith('REM DISCID'):
                discID = line[10:].strip().strip("")
                apply_tags('DiscID',discID)

    def write_chapters_to_mpc(self,destination : str):
        if not destination.endswith('.mpc'):
            raise MpcChapException('Destination must be *.mpc')
        elif self.chapters == []:
            raise MpcChapException('No chapters loaded. Nothink to write.')
        elif not os.path.exists(destination):
            raise MpcChapException('Destination file "{}" does not exist!'.format(destination))


        logging.debug('write_chapters_to_mpc: destination: {}'.format(destination))
        ini_tmp = tempfile.mktemp(prefix='pmc-',suffix='.ini')
        logging.debug('write_chapters_to_mpc:ini tempfile: {}'.format(ini_tmp))
        ini = open(ini_tmp,'wt')
        for (start_sample,chapter) in zip(self.start_samples,self.chapters):
            ini.write('[{}]\n'.format(start_sample))
            ini.write('gain=0\npeak=0\n')
            for tag in chapter.keys():
                ini.write('{}="{}"\n'.format(tag,chapter[tag]))
            # program comment
            #if not 'Comment' in chapter.keys():
            #    ini.write('Comment="Tagged with PyMpcChap"\n')
            ini.write('\n')
        ini.close()

        mpcchap_args = ['mpcchap',destination,ini_tmp]
        logging.debug(' '.join(mpcchap_args))
        devnull = open('/dev/null',mode='wb')
        result = subprocess.call(mpcchap_args,stdout=devnull,stderr=devnull)
        if result != 0:
            raise MpcChapException('mpcchap error')

        os.remove(ini_tmp)

